import { inject as service } from '@ember/service';
import C from 'ui/utils/constants';
import {
  get, set, computed, observer, setProperties
} from '@ember/object';
import { task, timeout } from 'ember-concurrency';
import { isBlank } from '@ember/utils';
import SearchableSelect from '../searchable-select/component';
import { alias } from '@ember/object/computed';
import { later } from '@ember/runloop';
import { on } from '@ember/object/evented';
import { isAlternate, isMore, isRange } from 'ui/utils/platform';
import $ from 'jquery';

const DEBOUNCE_MS = 250;

export default SearchableSelect.extend({
  globalStore:         service(),
  classNames:          'principal-search',
  errors:              null,
  _principals:         null,
  useLabel:            null,

  showDropdownArrow:   false,
  clientSideFiltering: false,
  loading:             false,
  focused:             false,
  selectExactOnBlur:   true,
  includeLocal:        true,
  sendAfterLoad:       false,
  searchOnlyGroups:    false,

  content:             alias('filteredPrincipals'),
  value:               alias('filter'),

  init() {
    this._super(...arguments);
    set(this, 'allUsers', get(this, 'globalStore').all('user'));
  },

  didInsertElement() {
    // Explicitly not calling super here to not show until there's content this._super(...arguments);

    $(this.element).find('input').on('focus', () => {
      if (this.isDestroyed || this.isDestroying) {
        return;
      }

      set(this, 'focused', true);
      const term = get(this, 'value');

      if ( term ) {
        set(this, '_principals', []);
        this.search.perform(term);
        this.send('show');
      }
    });

    $(this.element).find('input').on('blur', () => {
      later(() => {
        if (this.isDestroyed || this.isDestroying) {
          return;
        }

        set(this, 'focused', false);
        if ( get(this, 'selectExactOnBlur') ) {
          this.scheduleSend();
        }

        this.send('hide');
      }, 250);
    });
  },

  actions: {
    search(term, e) {
      const kc = e.keyCode;

      this.send('show');

      if ( kc === C.KEY.CR || kc === C.KEY.LF ) {
        this.scheduleSend();

        return;
      }

      var isAlpha = (k) => {
        return !get(this, 'metas').includes(k)
          && !isAlternate(k)
          && !isRange(k)
          && !isMore(k);
      }

      if (isAlpha(kc)) {
        set(this, 'principal', null);
        this.add();
        this.search.perform(term);
      }
    },

    show() {
      if (get(this, 'showOptions') === true) {
        return;
      }
      const toBottom = $('body').height() - $(this.element).offset().top - 60;  // eslint-disable-line


      setProperties(this, {
        maxHeight:   toBottom < get(this, 'maxHeight') ? toBottom : get(this, 'maxHeight'),
        showOptions: true,
      })
    },

    hide() {
      setProperties(this, {
        filter:          get(this, 'displayLabel'),
        showOptions:     false,
        '$activeTarget': null,
      })
    },
  },

  filteredPrincipals: computed('_principals.@each.{id,state}', function() {
    return ( get(this, '_principals') || [] ).map(( principal ) => {
      // console.log({label: get(principal, 'displayName') || get(principal, 'loginName') || get(principal, 'name'), value: get(principal, 'id'), provider: get(principal, 'provider'),});
      return {
        label:     get(principal, 'displayName') || get(principal, 'loginName') || get(principal, 'name'),
        value:     get(principal, 'id'),
        provider:  get(principal, 'provider'),
        type:      get(principal, 'principalType'),
        principal,
      };
    });
  }),

  externalChanged: on('init', observer('external', function(){
    let principal = get(this, 'external');

    if (principal) {
      setProperties(this, {
        readOnly:        true,
        optionValuePath: 'label',
      });

      this.setSelect({
        label:    get(principal, 'displayName') || get(principal, 'loginName') || get(principal, 'name'),
        value:    get(principal, 'id'),
        provider: get(principal, 'provider'),
        type:     get(principal, 'principalType')
      });
    }
  })),

  metas: computed(() => {
    return Object.keys(C.KEY).map((k) => C.KEY[k]);
  }),

  displayLabel: computed('interContent.[]', 'intl', 'localizedLabel', 'optionLabelPath', 'optionValuePath', 'prompt', 'value', function() {
    const value = get(this, 'value');

    if (!value) {
      return null;
    }

    const vp = get(this, 'optionValuePath');
    const lp = get(this, 'optionLabelPath');
    const selectedItem = get(this, 'interContent').filterBy(vp, value).get('firstObject');

    if (selectedItem) {
      let label = get(selectedItem, lp);

      if (get(this, 'localizedLabel')) {
        label = get(this, 'intl').t(label);
      }

      return label;
    }

    return value;
  }),

  showMessage: computed('filtered.[]', 'value', function() {
    if ( !get(this, 'value') ) {
      return false;
    }

    return get(this, 'filtered.length') === 0;
  }),

  scheduleSend() {
    if ( get(this, 'loading') ) {
      set(this, 'sendExactAfterSearch', true);
    } else {
      set(this, 'sendExactAfterSearch', false);
      this.sendSelectExact();
    }
  },

  sendSelectExact() {
    const value = get(this, 'value');
    const match = get(this, 'filteredPrincipals').findBy('label', value);

    let principal = null;

    if ( match ) {
      principal = match.principal;
    } else {
      set(this, 'value', '');
    }

    this.selectExact(principal);
    this.send('hide');
  },

  setSelect(item) {
    const gp = get(this, 'optionGroupPath');
    const vp = get(this, 'optionValuePath');

    set(this, 'value', get(item, vp));
    if (gp && get(item, gp)) {
      set(this, 'group', get(item, gp));
    }

    set(this, 'filter', get(this, 'displayLabel'));

    set(this, 'principal', item);
    this.add();
    this.send('hide');
  },

  search: task(function * (term) {
    if (isBlank(term)) {
      setProperties(this, {
        '_principals': [],
        loading:       false,
      });

      return;
    }

    // Pause here for DEBOUNCE_MS milliseconds. Because this
    // task is `restartable`, if the principal starts typing again,
    // the current search will be canceled at this point and
    // start over from the beginning. This is the
    // ember-concurrency way of debouncing a task.

    set(this, 'loading', true);

    yield timeout(DEBOUNCE_MS);

    let xhr = yield this.goSearch.perform(term);

    let neu = [];

    if ( xhr.status >= 200 && xhr.status <= 299 && xhr.body && typeof xhr.body === 'object' && xhr.body.data ) {
      neu = xhr.body.data;
    }

    if ( get(this, 'includeLocal') ) {
      let normalizedTerm = term.toLowerCase().trim();
      let foundIds = {};

      neu.forEach((x) => {
        foundIds[x.id] = true;
      })

      let local = get(this, 'allUsers');

      local = local.filter((x) => {
        if ( (x.name || '').toLowerCase().trim().startsWith(normalizedTerm) ||
             (x.username || '').toLowerCase().trim().startsWith(normalizedTerm) ) {
          for ( let i = 0 ; i < x.principalIds.length ; i++ ) {
            if ( foundIds[ x.principalIds[i] ] ) {
              return false;
            }
          }

          return true;
        }

        return false;
      });

      const globalStore = get(this, 'globalStore');

      local = local.map((x) => {
        return globalStore.getById('principal', x.principalIds[0]);
      });
      local = local.filter((x) => !!x);
      neu.addObjects(local);
    }

    set(this, '_principals', neu);

    return xhr;
  }).restartable(),


  goSearch: task(function * (term) {
    const { globalStore } = this;
    const data            = { name: term };

    if (this.searchOnlyGroups) {
      set(data, 'principalType', 'group')
    }

    try {
      return yield globalStore.rawRequest({
        url:    'principals?action=search',
        method: 'POST',
        data
      });
    } catch (xhr) {
      set(this, 'errors', [`${ xhr.status }: ${ xhr.statusText }`]);
    } finally {
      set(this, 'loading', false);
      if ( get(this, 'sendExactAfterSearch') ) {
        this.scheduleSend();
      }
    }
  }),

  add() {
    throw new Error('add action is required!');
  },

  selectExact() {
    throw new Error('selectExact action is required!');
  }

});
